<?php

namespace app\common\helper;

use app\common\logic\common\CommonLogic;
use app\common\model\Goods;
use app\common\model\Group;
use app\common\model\GroupOrder;
use app\common\model\Order;
use app\common\model\Pay;
use app\common\model\Refund;
use app\common\model\User;
use app\common\model\Vip;
use app\common\model\VipOrder;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Promise\RejectionException;
use think\Exception;
use think\facade\Db;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Transformer;
use WeChatPay\Util\PemUtil;

class NetPay
{
    // 微信回调  V3 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml 说明地址
    // https://github.com/wechatpay-apiv3/wechatpay-php
    // https://gitee.com/lipengwei_ha/wechatpay-php#%E5%9B%9E%E8%B0%83%E9%80%9A%E7%9F%A5
    // 官方更新了 V3 使用更方便，下次使用时更新
    // 安装 composer require wechatpay/wechatpay
    public function notify()
    {
        file_put_contents("pay.txt", "start pay\r\n",FILE_APPEND);
        $Config = cache('config');
        $inWechatpaySignature = request()->header('Wechatpay-Signature');
        $inWechatpayTimestamp = request()->header('Wechatpay-Timestamp');
        $inWechatpayNonce  = request()->header('Wechatpay-Nonce');
        $inWechatpaySerial = request()->header('Wechatpay-Serial');
        $inBody = file_get_contents('php://input');
//        $apiv3Key = 'yqFpBLj64m2G60U0xwx1zh8kWP2ukPNO';//
        $apiv3Key = $Config['wx_v3key'];
// 根据通知的平台证书序列号，查询本地平台证书文件，
// 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
        $basePath = app()->getRootPath() . "cert";
        $platformCertificateFilePath = "file://{$basePath}/wechatpay_key.pem";// 注意 `file://` 开头协议不能少
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

// 检查通知时间偏移量，允许5分钟之内的偏移
        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串
            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $platformPublicKeyInstance
        );

        if ($timeOffsetStatus && $verifiedStatus) {
            // 转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);
            // 使用PHP7的数据解构语法，从Array中解构并赋值变量
            ['resource' => [
                'ciphertext'      => $ciphertext,
                'nonce'           => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;
            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);

            // 把解密后的文本转换为PHP Array数组
            $inBodyResourceArray = (array)json_decode($inBodyResource, true);
            $out_trade_no = $inBodyResourceArray["out_trade_no"];
            $trade_state = $inBodyResourceArray["trade_state"];
            $pay_time = strtotime($inBodyResourceArray["success_time"]);
            $pay = Pay::where("out_trade_no",$out_trade_no)->find();
            if ($pay) {
                if ($trade_state == "SUCCESS") {
                    if ($pay->status != -1 ) {
                        return json(["code"=>"SUCCESS","msg"=>"成功"]);
                    }

                    $pay->pay_time = $pay_time;
                    $pay->status = 1;
                    $pay->save();

                    Db::startTrans();
                    try {
                        $order = VipOrder::where("id",$pay->order_id)->find();
                        if ($order) {
                            $order->status = 1;
                            $order->save();
                            $user = User::where("id",$order->user_id)->find();
                            if ($order->type == 'wanx') {
                                $user->xianqi += $order->xianqi;
                            } elseif ($order->type == 'qwen') {
                                $user->qwen_token += $order->xianqi;
                            }
                            $user->save();
                        }
//                        if (!empty($user->pid)) { // 推荐人
//                            $p_user = User::where("id",$user->pid)->find();
//                            $p_user->use_num += $vip->give_num;
//                            $p_user->save();
//                        }
                        Db::commit();
                    } catch (\Exception $exception) {
                        Db::rollback();
                        file_put_contents("pay.txt",$exception->getMessage() . "\r\n",FILE_APPEND);
                    }
                } elseif ($trade_state == "CLOSED") {
                    $pay->status = -2;
                    $pay->save();
                }
                return json(["code"=>"SUCCESS","msg"=>"成功"]);
            }
        }
        return json(["code"=>"FAIL","msg"=>"失败"]);
    }
    
    public function refund_notify()
    {
        $Config = cache('config');
        $inWechatpaySignature = request()->header('Wechatpay-Signature');
        $inWechatpayTimestamp = request()->header('Wechatpay-Timestamp');
        $inWechatpayNonce  = request()->header('Wechatpay-Nonce');
        $inWechatpaySerial = request()->header('Wechatpay-Serial');
        $inBody = file_get_contents('php://input');
//        $apiv3Key = 'yqFpBLj64m2G60U0xwx1zh8kWP2ukPNO';//
        $apiv3Key = $Config['wx_v3key'];
// 根据通知的平台证书序列号，查询本地平台证书文件，
// 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
        $basePath = app()->getRootPath() . "cert";
        $platformCertificateFilePath = "file://{$basePath}/wechatpay_key.pem";// 注意 `file://` 开头协议不能少
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

// 检查通知时间偏移量，允许5分钟之内的偏移
        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串
            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $platformPublicKeyInstance
        );
        
        if ($timeOffsetStatus && $verifiedStatus) {
            // 转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);
            // 使用PHP7的数据解构语法，从Array中解构并赋值变量
            ['resource' => [
                'ciphertext'      => $ciphertext,
                'nonce'           => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;
            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
            
            // 把解密后的文本转换为PHP Array数组
            $inBodyResourceArray = (array)json_decode($inBodyResource, true);
            $out_trade_no = $inBodyResourceArray["out_trade_no"];
            $out_refund_no = $inBodyResourceArray["out_refund_no"];
            $refund_status = $inBodyResourceArray["refund_status"];
            $success_time = strtotime($inBodyResourceArray["success_time"]);
            $refund = Refund::where("out_refund_no",$out_refund_no)->find();
            if ($refund) {
                if ($refund_status == "SUCCESS") {
                    if ($refund->status != -1 ) {
                        return json(["code"=>"SUCCESS","msg"=>"成功"]);
                    }
                    $refund->status = 1;
                    $refund->success = $success_time;
                    $refund->save();
                    
                    Db::startTrans();
                    try {
                        $goodsOrder = GroupOrder::where("id",$refund->order_id)->find();
                        if ($goodsOrder) {
                            $goodsOrder->is_refunded = 1;
                            $goodsOrder->status = -2;
                            $goodsOrder->save();
                            if ($goodsOrder->group_id != 0) {
                                $group = Group::where('id',$goodsOrder->group_id)->find();
                                if ($group) {
                                    if ($group->status != 2) {
                                        $group->now_num --;
                                        $group->save();
                                    }
                                }
                            }
                        }
                        Db::commit();
                    } catch (\Exception $exception) {
                        Db::rollback();
                    }
                } elseif ($refund_status == "CLOSED") {
                    $refund->status = -2;
                    $refund->save();
                }
                return json(["code"=>"SUCCESS","msg"=>"成功"]);
            }
        }
        return json(["code"=>"FAIL","msg"=>"失败"]);
    }
    
    
    // 微信初始化
    public function pay_init()
    {
        $Config = cache('config');
        $basePath = app()->getRootPath() . 'cert';
//        $merchantId = $this->Config["wx_merchant_id"];
        $merchantId = $Config['wx_mchid'];
        $merchantPrivateKeyFilePath = "file://{$basePath}/apiclient_key.pem";// 注意 `file://` 开头协议不能少
// 加载商户私钥
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        $merchantCertificateSerial = '1E186EB521B4C4246E4000453FC7F17119EE59A1';// API证书不重置，商户证书序列号就是个常量
// // 也可以使用openssl命令行获取证书序列号
// // openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}'
// // 或者从以下代码也可以直接加载
// // 「商户证书」，文件路径假定为 `/path/to/merchant/apiclient_cert.pem`
        $merchantCertificateFilePath = "file://{$basePath}/apiclient_cert.pem";// 注意 `file://` 开头协议不能少
        // // 解析「商户证书」序列号
        $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateFilePath);

// 「平台证书」，可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem`
        $platformCertificateFilePath = "file://{$basePath}/wechatpay_key.pem";// 注意 `file://` 开头协议不能少
// 加载「平台证书」公钥
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 解析「平台证书」序列号，「平台证书」当前五年一换，缓存后就是个常量
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);

// 工厂方法构造一个实例
        $instance = Builder::factory([
            'mchid'      => $merchantId,
            'serial'     => $merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs'      => [
                $platformCertificateSerial => $platformPublicKeyInstance,
//                'any' => null
            ],
            // APIv2密钥(32字节)--不使用APIv2可选
            'secret' => 'oy0NMSG41G5OzrzJGOsXg2WIcsJ9ikvL',// 值为占位符，如需使用APIv2请替换为实际值
            'merchant' => [// --不使用APIv2可选
                //     // 商户证书 文件路径 --不使用APIv2可选
                'cert' => "{$basePath}/apiclient_cert.pem",
                //     // 商户API私钥 文件路径 --不使用APIv2可选
                'key' => "{$basePath}/apiclient_key.pem",
            ],
        ]);
        return $instance;
    }
    // 小程序拉起支付字符串
    public function getPayStr($prepay_id)
    {
        $Config = cache('config');
        $base = app()->getRootPath() . "cert";
        $merchantPrivateKeyFilePath = "file://{$base}/apiclient_key.pem";
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
        $params = [
            'appId'     => $Config['wx_appid'],
            'timeStamp' => (string)Formatter::timestamp(),
            'nonceStr'  => Formatter::nonce(),
            'package'   => "prepay_id={$prepay_id}",
        ];
        $params += ['paySign' => Rsa::sign(
            Formatter::joinedByLineFeed(...array_values($params)),
            $merchantPrivateKeyInstance
        ), 'signType' => 'RSA'];
        return $params;
    }

    public function createWxPayV2($description,$out_trade_no,$pay_money,$openid)
    {
        $Config = cache('config');
        try {
            $v2 = [
                "appid" => $Config["wx_appid"],
                "mch_id" => $Config["wx_mchid"],
                "body" => $description,
                "out_trade_no" => $out_trade_no,
                "notify_url" => request()->domain() . "/api/index/notify.html",
                "total_fee" => round($pay_money * 100) ,
                "openid" => $openid,
                "spbill_create_ip" => "192.168.1.1"
            ];
            $pay = $this->pay_init();
            $res = $pay->v2->pay->unifiedorder
                ->postAsync([
                    "xml"=>$v2,
                ])
                ->then(static function($response) {
                    return Transformer::toArray((string)$response->getBody());
                })
                ->otherwise(static function($e) {
                    // 更多`$e`异常类型判断是必须的，这里仅列出一种可能情况，请根据实际对接过程调整并增加
                    if ($e instanceof \GuzzleHttp\Promise\RejectionException) {
                        return Transformer::toArray((string)$e->getReason()->getBody());
                    } else {
                        if ($e instanceof Exception) {
                            throwError($e->getMessage().$e->getTraceAsString());
                        }
                        return  [11];
                    }
                })
                ->wait();
            print_r($res);
            throwError('失败');
            return $this->getPayStr($res["prepay_id"]);
        } catch (Exception | RejectionException $exception) {
            throwError($exception->getMessage().$exception->getTraceAsString());
//            throwError('11');
        }
    }


    public function createWxPay($description,$out_trade_no,$pay_money,$openid)
    {
        $Config = cache('config');
    
        try {
            $v3 = [
                "appid" => $Config["wx_appid"],
                "mchid" => $Config["wx_mchid"],
                "description" => $description,
                "out_trade_no" => $out_trade_no,
                "notify_url" => request()->domain() . "/api/index/notify.html",
                "amount" => [
                    "total" => round($pay_money * 100),
                    "currency" => "CNY"
                ],
                "payer" => [
                    "openid" => $openid
                ]
            ];
            $pay = $this->pay_init();
            $res = $pay->v3->pay->transactions->jsapi
                ->post(["json"=>$v3]);
            $res = json_decode($res->getBody(),true);
            return $this->getPayStr($res["prepay_id"]);
        } catch (Exception | ClientException $exception) {
            throwError($exception->getMessage());
//            throwError('11');
        }
    }
    
    /**
     * @param $description
     * @param $out_trade_no
     * @param $pay_money
     * @return string
     * author 小李子 2023-10-28 13:46
     * description 生成PC扫码支付
     */
    public function createWxNativePay($description,$out_trade_no,$pay_money)
    {
        $Config = cache('config');
        try {
            $v3 = [
                "appid" => $Config["wx_appid"],
                "mchid" => $Config["wx_mchid"],
                "description" => $description,
                "out_trade_no" => $out_trade_no,
                "notify_url" => request()->domain() . "/api/index/notify.html",
                "amount" => [
                    "total" => round($pay_money * 100),
                    "currency" => "CNY"
                ]
            ];
            $pay = $this->pay_init();
            $res = $pay->v3->pay->transactions->native
                ->post(["json"=>$v3]);
            $res = json_decode($res->getBody(),true);
            return $this->createPayQrcode($res["code_url"]);
        } catch (Exception $exception) {
            throwError($exception->getMessage().$exception->getTraceAsString());
//            throwError('11');
        }
    }

    protected function createPayQrcode($uri)
    {
        $qr = new QrcodeHelper();
        return $qr->create($uri);
    }
    
    public function createWxRefund($out_refund_no,$out_trade_no,$refund_money,$total)
    {
        try {
            $v3 = [
                "out_refund_no" => $out_refund_no,
                "out_trade_no" => $out_trade_no,
                "notify_url" => request()->domain() . "/api/index/refund_notify.html",
                "amount" => [
                    "total" => round($total * 100) ,
                    "refund" => round($refund_money * 100) ,
                    "currency" => "CNY"
                ],
            ];
            $pay = $this->pay_init();
            $res = $pay->v3->refund->domestic->refunds
                ->post(["json"=>$v3]);
            $res = json_decode($res->getBody(),true);
            return $res['status'] ?? "";
        } catch ( Exception  $exception) {
            throwError($exception->getMessage().$exception->getTraceAsString());
        }
    }
    
    
    // 支付宝
    // 安装 composer require alipaysdk/easysdk:^2.0
    // https://github.com/alipay/alipay-easysdk/tree/master/php
    // https://gitee.com/lipengwei_ha/alipay-easysdk/tree/master/php

    // 支付宝初始化
    public function alipayInit()
    {
        $options = new Config();
        $options->protocol = 'https';
        $options->signType = 'RSA2';

//        $options->gatewayHost = 'openapi.alipaydev.com/gateway.do'; // 沙箱
//        $options->appId = '2021001168664438'; // 沙箱

        $options->appId = '2021003111679761';
        $options->gatewayHost = 'openapi.alipay.com';

        // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
        // <-- 请填写您的应用私钥，例如：MIIEvQIBADANB ... ... -->
        $options->merchantPrivateKey = 'MIIEpAIBAAKCAQEAtQ0wlLAXza+9CV3GAxjZlayeU9S/Jt8Vmaie0AAetUZ9NRYSM2858qXL7SLlmq7vKZUu2uJTwzCaOQd48fvcXX/8AwIxn8AMWNE+KnSh1atTSSfaM30QN2uhZgPwhKQV3NlMD0I2MXfSEYCx4YPu2xMqhNQtuN+oECfUfE7ERmD3bZpMLwjP+o2d4oS1ddbYa1U099FzL1+scnmofAgGjauFWSv/rhnHLMTdv5ch7N/XUrVYvRR2hJP4ni8X0T4151PNuW30NCMjPRamZFoc2NAgI0cgfF4UwJnadMKrnApK+OuiGidPmDe4kLM9/kEe8GXuyvaSafXX5Jfl4wdLQQIDAQABAoIBAD0bCYV1dWM3Z/7MRnNvJKiujPVQPX+6FUk6LveWwsZ91u5e15/Cb3Vk3dHYUFQHrLKFJj472vdq5PJQglJxpetssbq2N9dp8wDbBjdADArYexCawbtU/28lBWEv/TJXSwWx1UjlGPNTNHQx0leBttD7xAL2tcPKwKdLOh0GXJo/t5wxszxN9vdr2/A1q1vhoY+2uf750IqOCB6I0BU6wAYicmWm3f4Q8Plr02T/nRFV5qH/M+6o8wz8f/SxlbjY+HkD1Y6lhZdxnQuYnAMpBsE7MALYaMmjSqUXUh8cXbNdwKiatDm+E3qUXFzK29wwmUCKoWpi5HHWw6jDVZyU2x0CgYEA8V/MHhWGC3aixrp9soeOHFW22ksYF9yewECSGXjlyKumdUb+QzqDBOpz8+ZAK8Ax3FaHiUpCPiApkx3jGtNn0yjuBm+imyPufGyEWaKBPnlZCM3mbL5+2iBC55iILX7XGpT/h/yv/VEDgdXysVBqumZHV6Gg1JCNIqNGPRrm5J8CgYEAwAWqWrRySJFDj4x4EaVwYZu+WPEsZ86b/posXnukRR4pxMJoxDTq8iWmchjqDUDRfTPfsDNx02Z+FYkq6s3eJeOaiV/pLqtEvrw6k0RpCEpDQtPLBd5Y18JDQl3z/8E9vnc2JyRZV8Gq8x0jW9ZUKOALYOdKnwUWAfurDPoM5B8CgYBK/XFEE3nOy+/sf/oXYkgDg5nInjYBC9kCb800PrxdKP8RRpouNtqHx3N43hsnvJGBHB0FyV1lQcbI3ys0pWOHVhsFQt+pBXPNkkVYN8WhrD++HYIuXMy6ZfWDUH9WaybXKl1tWVMwB2BokRjSf+J4wNf/YMa/450FmuZyi44ajwKBgQCSfwADqW0THfgsOR+VXuFJJnfnBUA6hi8O4/J7ttqRfLyjByrEnhas3LqKi6VsrQnl+kBq/iSWAriol3htoNgdkfJhYhHVcAbHeXU6y1NSZs2/2zMl7AddvDByHpm7gmMnv21KGXor8G4Kgi1qsC6O7LbhnDOa9Fdg9e7+vHo/QwKBgQDYXPMJYtl4V7xDHGRRPzBRbE6eeWzk/7pHgbn7FKl7teUwxYkoZ0XseeSBN2/JVL9mAypwMFUYuMIlclir7L0o2xlgVlnUX6yvRkKmWoYGCCet0MBlgC0HF8TcYzQ3CnAme9fR+W8CjPVnsqas95iCRK6fx9dsyWFNbNYG8uTdAA==';
        //<-- 请填写您的支付宝公钥证书文件路径，例如：/foo/alipayCertPublicKey_RSA2.crt -->
        $options->alipayCertPath = '';
        // <-- 请填写您的支付宝根证书文件路径，例如：/foo/alipayRootCert.crt" -->
        $options->alipayRootCertPath = '';
        //<-- 请填写您的应用公钥证书文件路径，例如：/foo/appCertPublicKey_2019051064521003.crt -->
        $options->merchantCertPath = '';

        //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
         $options->alipayPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjfU7WpagUnbcsfSxV6OjH8JzQ+mzRpYA5G+0VDsCWbw3RbgbU45Zr+IwLl7QnNmXCg7LcuL48+9y0N0r/psLe4cvq3/dU+DaAPFtWahnsGTALTvqMrNgOZX4XV6E6GyR+VoUbSsViYKYIOcWbaVUtphKFsoxRn4AhH1jA0v6ZIN082gaEcL+eUKYjo+h5Je+d4O0IBsPgKsnExRCDE8ZquFRX7nQRM48Gy5ZgLQWXCti7+HPMJl6D6/AXGpYfoVZ3nnzoRFQDkMQwXxRIwrhPh5eMF+aFLv3hHjNZjePKd/U/80KYd8s52am/DMAVhUsBK03Q9U99gdfRwWaDAXa0QIDAQAB';
        //可设置异步通知接收服务地址（可选）
        $url = request()->domain() . (string) url("Pay/alipayNotify");
        $options->notifyUrl = $url;

        //可设置AES密钥，调用AES加解密相关接口时需要（可选）
        // <-- 请填写您的AES密钥，例如：aa4BtZ4tspm2wnXLb1ThQA== -->
        $options->encryptKey = "";
        return $options;
    }
}